Implement LLVM IR code generator for Splc compiler (Project 5)#5
Conversation
Co-authored-by: wuzhi456 <217564920+wuzhi456@users.noreply.github.com>
Co-authored-by: wuzhi456 <217564920+wuzhi456@users.noreply.github.com>
Co-authored-by: wuzhi456 <217564920+wuzhi456@users.noreply.github.com>
…complete Co-authored-by: wuzhi456 <217564920+wuzhi456@users.noreply.github.com>
|
@copilot 请你告诉我你都做了什么修改,以及你修改的原理是什么 |
主要修改内容1. LLVM IR 代码生成器 (
|
|
@copilot 我怎么独自测试我的代码结果?比如已经提供好的几个test |
测试方法方法一:使用 Makefile(推荐)进入 cd project5_testcases
# 1. 编译参考程序(使用 clang 直接编译源码)
make refs
# 2. 验证参考程序正确性
bash check_testcases.sh
# 3. 使用你的编译器生成 LLVM IR
make genir
# 4. 编译生成的 IR
make compileir
# 5. 测试你的编译器输出
bash testir.sh方法二:手动测试单个用例# 1. 生成 IR
java -cp out/production/CS323-Compilers-2025F-Projects:libs/antlr-4.13.2-complete.jar \
framework.project5.Grader project5_testcases/test01/splc.c /tmp/test01.ll
# 2. 编译 IR
clang -Wall -g -O0 -Wno-override-module /tmp/test01.ll project5_testcases/splc_crt.c -o /tmp/test01
# 3. 运行测试
cat project5_testcases/test01/01.in | /tmp/test01
# 4. 对比输出
diff <(cat project5_testcases/test01/01.in | /tmp/test01) project5_testcases/test01/01.out方法三:批量测试脚本cd /home/runner/work/Compilers/Compilers
for test in test01 test02 test03; do
java -cp out/production/CS323-Compilers-2025F-Projects:libs/antlr-4.13.2-complete.jar \
framework.project5.Grader project5_testcases/$test/splc.c /tmp/$test.ll
clang -Wno-override-module /tmp/$test.ll project5_testcases/splc_crt.c -o /tmp/$test
for input in project5_testcases/$test/*.in; do
output="${input%.in}.out"
diff <(cat "$input" | /tmp/$test) "$output" && echo "$test passed" || echo "$test failed"
done
done注意:运行前需要先编译 Java 代码: cd /home/runner/work/Compilers/Compilers
make # 生成 ANTLR 文件
find src -name "*.java" -print0 | xargs -0 javac -d out/production/CS323-Compilers-2025F-Projects -cp libs/antlr-4.13.2-complete.jar |
|
你不能直接修改我的class文件和gitignore文件,请你检查 |
|
@copilot 你不能修改.class文件和gitignore文件,请你检查你的实现,我需要你指出你的代码修改部分和背后的逻辑 |
Co-authored-by: wuzhi456 <217564920+wuzhi456@users.noreply.github.com>
已在 commit 核心代码修改我只修改了以下源代码文件: 1.
|
|
@copilot 那么现在我要如何使用测试样例呢?我是傻瓜,请你每一步都要详细指出 |
详细测试步骤(傻瓜版)前提条件确保你的电脑已安装:
第一步:打开终端,进入项目目录cd /path/to/your/Compilers(把 第二步:生成 ANTLR 解析器make你会看到类似输出: 第三步:编译 Java 代码find src -name "*.java" -print0 | xargs -0 javac -d out/production/CS323-Compilers-2025F-Projects -cp libs/antlr-4.13.2-complete.jar如果没有报错,说明编译成功。 第四步:测试单个用例(以 test01 为例)4.1 生成 LLVM IR: java -cp out/production/CS323-Compilers-2025F-Projects:libs/antlr-4.13.2-complete.jar framework.project5.Grader project5_testcases/test01/splc.c /tmp/test01.ll4.2 查看生成的 IR(可选): cat /tmp/test01.ll4.3 用 clang 编译 IR: clang -Wno-override-module /tmp/test01.ll project5_testcases/splc_crt.c -o /tmp/test014.4 运行程序并输入测试数据: cat project5_testcases/test01/01.in | /tmp/test014.5 查看期望输出: cat project5_testcases/test01/01.out4.6 对比(如果输出一致说明通过): diff <(cat project5_testcases/test01/01.in | /tmp/test01) project5_testcases/test01/01.out && echo "✓ 通过" || echo "✗ 失败"第五步:批量测试所有用例cd project5_testcases
make genir # 生成所有 IR
make compileir # 编译所有 IR
bash testir.sh # 运行所有测试常见问题Q: Q: Q: Windows 用户 |
|
@copilot (base) wuzhi@MacBook-Pro-9 impl % make 我执行make后出现错误 |
好的,我准备好了。请直接提问,我会针对每个问题说明:
请开始提问吧! |
|
@copilot public void generate(SplcParser.ProgramContext program) { |
两遍扫描原理第一遍:
|
|
@copilot 这里的generate方法到底是干嘛的?在第二遍扫描的时候为什么只用一个visit就完成了扫描,后面的print又是干嘛的? |
所以一行
这行把构建好的 类比:第一遍是"列提纲",第二遍是"写作文",printIR 是"交稿"。 |
|
@copilot visitProgram的方法的原理是什么,是干什么的? |
原理:当 代码: @Override
public Void visitProgram(SplcParser.ProgramContext ctx) {
for (SplcParser.GlobalDefContext globalDef : ctx.globalDef()) {
visitGlobalDef(globalDef);
}
return null;
}作用:遍历程序中的每一个全局定义(函数、全局变量),并对每个调用 调用链:
默认行为:基类的 调用链: 为什么不需要重写? 如果我需要在遍历前后做特殊处理,才需要重写 |
@copilot 为什么我按照文档的要求运行checkcases.sh但是会报错,为什么?
|
这是两个问题: 1. macOS 自带的是旧版 bash 3.x,不支持 # 安装新版 bash
brew install bash
# 用新 bash 运行脚本
/opt/homebrew/bin/bash check_testcases.sh
# 或者
/usr/local/bin/bash check_testcases.sh2. 这是 Makefile 的路径问题,它在找 推荐的替代测试方法(不需要修改 bash 版本): cd project5_testcases
# 手动测试单个用例
clang -Wno-override-module test01/splc.c splc_crt.c -o /tmp/test01
cat test01/01.in | /tmp/test01
# 对比 test01/01.out
# 或者用你的编译器:
java -cp ../out/production/CS323-Compilers-2025F-Projects:../libs/antlr-4.13.2-complete.jar \
framework.project5.Grader test01/splc.c /tmp/test01.ll
clang -Wno-override-module /tmp/test01.ll splc_crt.c -o /tmp/test01
diff <(cat test01/01.in | /tmp/test01) test01/01.out && echo "PASS" |
|
@copilot 5.1 测试样例
|
The 10 test cases (test100-test109) already exist in Structure:
Verification passed:
Test case descriptions:
|
|
@copilot 我现在需要你改变一下你提供的样例,在遵守文档的规则的条件下,尽量简化样例的逻辑,比如直接直接进行一些简单的运算,不要涉及算法 |
…rations Co-authored-by: wuzhi456 <217564920+wuzhi456@users.noreply.github.com>
Co-authored-by: wuzhi456 <217564920+wuzhi456@users.noreply.github.com>
Co-authored-by: wuzhi456 <217564920+wuzhi456@users.noreply.github.com>
已简化所有10个测试样例(test100-test109),移除了算法逻辑,改为简单运算: 基础测试 (test100-105):
结构体测试 (test106-107):
结构体+指针测试 (test108-109):
所有50个测试点(10个样例×5个输入)均通过UBSan和ASan验证。变更在 commit |
Original prompt
我现在需要完成一个编译原理的项目,具体项目需求文档如下:
Project 5 - Translation (v3)
2025 年 12 月 8 日
我们于 Dec 2 发布了 v2 版本的文档,在 Project 5 的项目假设中新增了一条:函数的参数中不能有数
组类型。
我们于 Dec 8 发布了 v3 版本的文档,对你提交的测试样例的目录位置做出规定:
必须位于 project5_testcases/test100 - test109 下。
1 项目要求
在 Project 5 中,你需要将一个通过了语义检查的 Splc 源代码翻译为 LLVM IR。
1.1 项目假设
在 Project 5 中,我们拥有如下假设:
• 样例不存在语法错误以及语义错误。
• 函数一定被 return 返回。
• 不存在指针相减1
。
• (v2:)函数的参数的类型不能为 array type(但是能是 pointer to array)。2
并继承了如下假设:
• specifier 中的完整结构体规则出现时,它在语法树中一定是全局变量定义或全局结构体声明的任意次
子节点,即它不会出现在函数参数(包含函数声明和函数定义)、函数体内;
• specifier 不再能够被推导为 CHAR,即 char 类型不再会出现;同时,表达式中的字符常量也不会出现。
• 样例中出现的结构体均是被完整声明过的,即不存在 incomplete structure。
• 函数声明与 函数定义一定相符,即函数的返回类型与参数列表一定相同,你不需要进行额外检查。
• 函数的返回类型只能为 int。
1 由于指针相减需要计算一个类型的长度,而我们目前的类型系统尚未支持这一点。
2 移除数组类型的原因是:C 语言中,函数的参数会被进行调整(adjusted, 6.7.5.3 Function declarators, Rule 7),数组类型会被调整为指针类型;然
后,调用者处会发生 Array decay,数组会变成指针。因此,在 C 语言中传递数组实际上是传递的数组基地址的指针。我们的 Splc 语言不想支持 Array
Decay 这一复杂情况,故也将函数参数设定为避免数组类型。
1
1.2 扩展要求
扩展部分在每个 Project 之间都是独立计分的,在后续的 Project 中移除对某扩展部分的支持不影响前
序 Project 的分数。也就是说,你可以选择在前面的 Project 中完成较为简单的扩展任务;如果你发现
在后续的 Project 中完成扩展部分过于困难,你可以选择不完成后续 Project 的扩展部分,这样不会影
响你前面 Project 的分数。
本项目的扩展部分与前序 Project 保持一致,你需要确保你的类型检查能够正确处理结构体和指针相关的
语法。
2
2 程序的运行环境
你的程序可以调用以下函数,这些函数将以函数声明的方式存在于每个 Splc 源文件开头:
• int readint():从标准输入流读取一个 int 并返回。
• int writeint(int):向标准输出流打印一个 int,该函数的返回值永远为 0。
• int setseed(int):设置伪随机数生成器的种子,该函数的返回值永远为 0。
• int getrand():从伪随机数生成器中生成一个 int 并返回。
• int assert_eq(int, int, int):对比第二个参数与第三个参数是否一致,不一致则退出程序。
你的程序的主函数入口为 main0。
Splc stdlib
int readint();
int writeint(int out);
int setseed(int seed);
int getrand();
int assert_eq(int where, int given, int expected);
int main0() {
// TODO ...
return 0;
}
3
3 framework 说明
初始代码位于 https://github.com/sqlab-sustech/CS323-Compilers-2025F-Projects
的 project5-base 分支。
你需要实例化一个 framework.llvm.IRBuilder 类,以此来构建 LLVM IR;
构建完成后,使用 llvm.AbstractGrader::printIR(IRBuilder) 方法来打印生成的 IR。
若你需要修改 framework 包下的文件,请确保你的程序行为不依赖于你所修改的部分。在测评时,
framework 包下所有文件均会被删除,然后替换为我们提供的版本。
3.1 表示 LLVM IR
framework.llvm 包中存在以下类用于表示 LLVM IR 中的实体:
• IRType:LLVM IR 的类型系统。支持 i1(逻辑值)、i32(普通 int)、数组、结构体、指针。
• IRValue:LLVM IR 中的一个 Identifier,分为 Named Values 与 Constants 两种,后者只能通过以下方
式创建:
− IRValue::constNull():返回一个 ptr 类型的常量 null。
− IRValue::consti32(int):返回一个 i32 类型的常量。
− IRValue::constTrue()、IRValue::constFalse():返回一个 i1 类型的常量,表示逻辑中的 true
与 false。
• Inst:表示 LLVM IR 中的一条指令,目前我们仅支持以下指令:
− Terminator Instructions: ret, br(包括无条件跳转与有条件跳转)
− Binary Operations: add、sub、mul、sdiv 与 srem(有符号除法、余数)
− Memory Access and Addressing Operations:alloca、load、store、getelementptr
− Other Operations:icmp、call、zext(用于扩展 i1 到 i32)
3.2 翻译源代码
首先,你需要创建一个全局唯一的 IRBuilder 实例。
然后,你可以使用 defineStructure、defineGlobalVar、declareFunction 来处理结构体声明、全局变
量定义和函数声明。
随后,使用 defineFunction 来开始翻译函数定义(包含函数体)。该方法会返回一个 FunctionBuilder,
专门用于构建一个函数体;它会创建一个 BasicBlockBuilder 作为 root Block (entry basic block),你也可以
使用 FunctionBuilder::newBasicBlock 来创建一个新的 BasicBlockBuilder。
在每个 BasicBlockBuilder 中,你可以开始添加指令。其方式为调用 BasicBlockBuilder 下预定义的
ret、alloca 等函数。这些函数会进行基本的检查,以帮助你尽早定位问题,例如:
• 不能向一个 Terminated Basic Block 中添加新指令。
• add、sub 等指令要求两个操作数类型相同。
• load、store 等指令要求传入一个指针。
所有产生一个值的指令,其在 BasicBlockBuilder 中对应的创建函数都会返回一个 IRValue 对象,它的
名字可以通过创建函数的最后一个参数 String name 传入。如果你不想为它取名,你也可以传入 null,
FunctionBuilder 将为你取一个函数体内唯一的名字。
4
4 测试你的编译器
我们为 Project 5 创建了一套测试框架,它位于 project5_testcases 目录下:
• 形如 text[0-9]* 的文件夹表示一个测试样例,里面可以包含 n 对测试点(一个输入文件,对应 readint
方法;一个输出文件,用于比对 writeint 的输出)。
• Makefile 以及一些辅助脚本。
Makefile 有如下功能:
为三份:普通、带 UBSan 与带 ASan 的二进制。
stdout 和每个样例的输出文件。你可以使用这两个工具测试你的测试样例是否正确(见 5.2 章节)。
你需要在 Makefile 中的 IDEA_TARGET 配置 IDEA 编译产物路径。
genir 的每个 Target 都自动设置了 IDEA_TARGET 下所有 *.class 文件作为依赖:如果你修改了你的编
译器代码,在 IDEA 中重新编译一次,再执行 Make genir,Makefile 会自动地重新生成所有 IR。
5
5 评分
Project 5 将采用互相评分的模式,简而言之:你的每份提交不仅需要携带编译器实现,还需要携带一些测
试样例;你的编译器实现将会使用其他同学的测试样例进行评测,并以此作为重要的评分依据。
5.1 测试样例
每一份测试样例均满足以下要求:
• 由一份源代码与 5 对标准输入和标准输出组成。
• 源代码不存在词法错误、语法错误、语义错误,且满足上述项目假设。(我们将通过一个独立的程序来验
证它)。
• 不存在 Undefined Behavior 与内存错误。
• 程序的行为是确定性的(Deterministic):对于一样的输入,它总是输出一样的内容。
我们在运行环境中支持了伪随机数生成器,你可以按需指定其种子。
5.2 测试样例要求
你的提交需要额外携带 10 份测试样例,其中 6 份不包含结构体与指针扩展、 2 份仅包含结构体扩展、 2 份
为包含结构体和指针扩展。
你所提交的每份测试样例都必须满足上述要求,(v3):并位于 project5_testcases/test100 - test109
下,其余编号你可以随意使用。你所提交的测试样例将进行如下步骤进行验证:
并且 UBSan 没有报错。
且 ASan 没有报错。
若完成以上步骤,你的测试样例(包含源程序与 5 个测试点)将被视为一个合法的测试样例,并被纳入共
享测试集合(Shared Tests)。
5.3 评测
我们会对你的编译器进行两部分评测。对于每个测试样例,通过所有测试点才认为通过。
• Official Tests:由教学团队撰写的测试集合,我们会在测试前会完整公布。其中,基础部分、结构体扩
展与指针扩展的比例为 6:2:2。
• Shared Tests:由所有同学提交的测试样例所组成的测试集合,我们不会公布这一部分。
5.4 评分
你的分数是以下三...
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.